0%

SpringAI — Tool Calling(工具调用)

工具调用概述

工具调用(Tool Calling,也称为 函数调用 Function Calling)是 AI 应用中的一种常见模式,允许模型与一组 API 或”工具”交互,从而扩展模型的能力。

工具主要用于以下场景:

  • 信息检索(Information Retrieval):此类工具用于从外部源(如数据库、Web 服务、文件系统或搜索引擎)检索信息,目的是增强模型的知识,使其能回答原本无法回答的问题,比如适用于 检索增强生成(RAG) 场景。工具的使用例子有“获取某地的当前天气”、“检索最新新闻文章”、“查询数据库中的特定记录”等等
  • 执行操作(Taking Action):此类工具可在软件系统中执行操作,如发送邮件、在数据库中创建记录、提交表单或触发工作流,旨在自动化原需人工干预或显式编程的任务。比如“为聊天机器人用户预订航班”、“填写网页表单”、“在代码生成场景中依据自动化测试实现 Java 类(TDD)”

虽然通常称”工具调用”是模型的能力,但实际上由客户端应用程序提供工具调用逻辑。模型只能请求调用工具并提供输入参数,应用程序负责执行工具并返回结果。模型永远不会直接接触被注册为工具的 API。


工具调用示例

场景:闹钟设置(如果经常用siri来预定闹钟,那么对这个场景就不会陌生)

涉及到两个简单工具:一个用于信息检索,一个用于执行操作。

  • 信息检索工具: 获取用户时区的当前日期和时间
  • 操作工具: 在指定时间设置闹钟

定义 @Tool

定义 DateTimeTools 类并实现两个工具,用 @Tool 注解标记方法并给出详细描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DateTimeTools {

@Tool(name = "getCurrentDateTime",
description = "获取用户时区的当前日期和时间")
public String getCurrentDateTime() {
System.out.println("[getCurrentDateTime]");
return ZonedDateTime.now(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}

@Tool(name = "setAlarm",
description = "在指定 ISO-8601 时间设置闹钟。例如:2026-06-29T15:30:00")
public void setAlarm(
@ToolParam(description = "ISO-8601 格式的闹钟时间") String time) {
System.out.println("[setAlarm]");
System.out.println("⏰ 闹钟已设置为:" + time);
}

}

注册并调用

通过 tools() 方法进行工具实例注册,ChatClient 内部会自动处理工具调用循环:

1
2
3
4
5
6
String answer = chatClient.prompt()
.user("请在 10 分钟后设置一个闹钟提醒我开会")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(answer);

执行结果

1
2
3
4
5
6
7
8
9
[getCurrentDateTime]
[setAlarm]
⏰ 闹钟已设置为:2026-06-29T17:41:00
闹钟已设置成功!⏰

- **当前时间**:2026年6月29日 17:31
- **闹钟时间**:2026年6月29日 **17:41**

届时闹钟会提醒您开会,请放心~

工具核心概念

Spring AI 通过一系列灵活的抽象层支持工具调用,使得能够以一致的方式定义、解析和执行工具。工具触发的整个过程如下:
tool-calling-invoke

  1. 应用将工具定义(名称 + 描述 + 入参 JSON Schema)随聊天请求发给模型
  2. 模型决定调用工具时,返回工具名称 + 入参
  3. 应用按工具名称来识别并根据提供的输入参数执行工具
  4. 应用程序会处理工具调用的结果
  5. 应用将工具执行结果返回给模型
  6. 模型使用工具调用结果作为附加上下文生成最终回复

整个过程中对工具调用的派发执行是由ToolCallingManager(DefaultToolCallingManager)来协调完成的,其中工具是工具调用的构建块,由 ToolCallback 接口建模;Spring AI 内置支持从方法(@Tool 注解)函数(Function/Supplier 等)生成 ToolCallback


基于方法的工具定义 — @Tool 注解(声明式)

@Tool 注解属性

属性 说明
name 工具名称,默认取方法名。同一请求中所有工具名称必须唯一
description 工具描述,强烈建议详细填写,影响模型是否及何时调用
returnDirect true = 结果直接返回调用方,不回传模型
resultConverter 自定义 ToolCallResultConverter

方法要求:

  • 方法可以是实例方法或静态方法,任意访问权限(public、protected、package-private 或 private)
  • 包含方法的类可以是顶层类或嵌套类,任意访问权限
  • 参数数量任意(包括无参),支持大多数类型:基本类型、POJO、Enum、List、数组、Map 等
  • 返回值可以是大多数类型,包括 void
  • 如果方法有返回值,返回类型必须是可序列化类型

@ToolParam 注解属性

属性 说明
description 参数描述,帮助模型理解如何使用该参数(格式、允许值等)
required 是否必填,默认 true

如果参数标注了 @Nullable,除非使用 @ToolParam(required = true) 显式标记,否则视为可选。

除了 @ToolParam,还可以使用 Swagger 的 @Schema 或 Jackson 的 @JsonProperty/@JsonClassDescription

示例:

1
2
3
4
5
6
@Tool(description = "根据城市名查询当前天气")
public String getWeather(
@ToolParam(description = "城市名称,如 'Beijing'", required = true)
String city) {
return weatherService.getCurrentWeather(city);
}

注册方式

单次请求注册:

1
2
3
4
chatClient.prompt()
.user("...")
.tools(new DateTimeTools())
.call();

默认工具(Builder 级别):

1
2
3
ChatClient chatClient = ChatClient.builder(model)
.defaultTools(new DateTimeTools())
.build();

通过 ChatModel 选项注册:

1
2
3
4
5
6
7
8
9
10
ChatModel model = ...;

Prompt prompt = new Prompt(
List.of(new UserMessage("...")),
ToolCallingChatOptions.builder()
.toolCallbacks(ToolCallbacks.from(new DateTimeTools()))
.build()
);

model.call(prompt);

如果同时提供了默认工具和运行时工具,运行时工具将完全覆盖默认工具

编程式生成 ToolCallback:

1
2
// 使用 ToolCallbacks 工具类
ToolCallback[] callbacks = ToolCallbacks.from(new DateTimeTools());

tools() 方法也直接接受 ToolCallbackToolCallbackProvider 实例,以及 @Tool 注解的 POJO 实例和这些类型的集合。


基于方法的工具定义 — 编程式(MethodToolCallback)

使用 MethodToolCallback.Builder 以编程方式构建工具:

1
2
3
4
5
6
7
8
9
MethodToolCallback callback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder()
.name("getCurrentDateTime")
.description("获取用户时区的当前日期时间")
.inputSchema("{\"type\":\"object\",\"properties\":{}}")
.build())
.toolMethod(DateTimeTools.class.getMethod("getCurrentDateTime"))
.toolObject(new DateTimeTools())
.build();

Builder 可配置项:

属性 说明
toolDefinition ToolDefinition 实例,定义工具名称、描述和输入 Schema(必填
toolMetadata ToolMetadata 实例,定义额外设置(如 returnDirect、resultConverter)
toolMethod 代表工具方法的 Method 对象(必填
toolObject 包含工具方法的对象实例(静态方法可省略)
toolCallResultConverter 自定义结果转换器

注册方式同声明式:

  • .tools(callback) — 单次请求
  • .defaultTools(callback) — 默认工具
  • ToolCallingChatOptions.toolCallbacks(callback) — ChatModel 选项

不支持作为工具方法参数/返回值的类型:Optional、异步类型(CompletableFutureFuture)、响应式类型(FlowMonoFlux)、函数式类型(FunctionSupplierConsumer)。函数式类型请使用函数式工具规范方式。


基于函数的工具定义 — FunctionToolCallback

支持 FunctionSupplierConsumerBiFunction 等函数式类型。

1
2
3
4
5
6
FunctionToolCallback.from(
"getWeather", // 工具名称
weatherService::get, // 函数对象
WeatherRequest.class, // 输入类型
ToolMetadata.builder().build()
);

Builder 可配置项:

属性 说明
name 工具名称(必填
toolFunction 函数对象 Function/Supplier/Consumer/BiFunction必填
description 工具描述
inputType 函数输入类型(必填
inputSchema 输入参数的 JSON Schema
toolMetadata 额外设置(returnDirect、resultConverter)
toolCallResultConverter 自定义结果转换器

函数要求:

  • 函数输入和输出可以是 Void 或 POJO
  • 输入和输出 POJO 必须是可序列化的
  • 函数及输入输出类型必须是 public

不支持作为函数入参/出参的类型:原始类型、集合类型(ListMapArraySet)、异步类型、响应式类型。原始类型和集合请使用基于方法的工具规范方式。


工具规范

ToolCallback 接口

ToolCallback 是 Spring AI 中工具的核心建模接口,包含工具的定义和执行逻辑:

1
2
3
4
public interface ToolCallback {
ToolDefinition getToolDefinition();
ToolCallResult invoke(ToolCall toolCall);
}

Spring AI 提供两个内置实现:

  • MethodToolCallback — 基于方法的工具
  • FunctionToolCallback — 基于函数的工具

ToolDefinition 接口

提供 AI 模型了解工具可用性所需的信息:

1
2
3
4
5
public interface ToolDefinition {
String name();
String description();
String inputSchema(); // JSON Schema
}

使用 ToolDefinition.Builder 构建:

1
2
3
4
5
ToolDefinition toolDef = ToolDefinition.builder()
.name("myTool")
.description("工具描述")
.inputSchema(jsonSchema)
.build();

JSON Schema 生成

Spring AI 通过 JsonSchemaGenerator 类内置支持为工具输入类型生成 JSON Schema,作为 ToolDefinition 的一部分提供给模型。

参数描述: 可通过以下注解为输入参数提供描述(按优先级排序):

注解 来源
@ToolParam(description = "...") Spring AI
@JsonClassDescription(description = "...") Jackson
@JsonPropertyDescription(description = "...") Jackson
@Schema(description = "...") Swagger

此方法对方法和函数均适用,且支持嵌套类型的递归使用。

必填/可选控制:默认每个输入参数都是必填的。可通过以下注解将参数标记为可选(按优先级排序):

注解 来源
@ToolParam(required = false) Spring AI
@JsonProperty(required = false) Jackson
@Schema(required = false) Swagger
@Nullable Spring Framework

工具结果转换(ToolCallResultConverter)

工具调用的结果通过 ToolCallResultConverter 序列化为字符串后回传给 AI 模型。

1
2
3
public interface ToolCallResultConverter {
String convert(Object result);
}
  • 默认使用 Jackson 序列化(DefaultToolCallResultConverter
  • 可实现自定义转换器

配置方式:

1
2
3
4
5
6
7
// 声明式:@Tool 注解
@Tool(description = "...", resultConverter = MyConverter.class)

// 编程式:Builder
MethodToolCallback.builder()
.toolCallResultConverter(new MyConverter())
.build();

工具上下文(ToolContext)

Spring AI 支持通过 ToolContext API 向工具传递额外的上下文信息。

1
2
3
4
5
6
7
8
9
10
11
12
// ChatClient
chatClient.prompt()
.toolContext(Map.of("userId", "12345", "sessionId", "abc"))
.user("...")
.call();

// ChatModel
ChatModel.call(Prompt.withOptions(
ToolCallingChatOptions.builder()
.toolContext(Map.of("userId", "12345"))
.build()
));

如果默认选项和运行时选项都设置了 toolContext,结果是两者的合并,运行时选项优先

Return Direct(直接返回)

默认情况下,工具调用的结果会回传给模型继续对话。但有时你希望结果直接返回给调用方,而不是发回模型。

典型场景:

  • RAG 工具的结果直接展示给用户,避免模型不必要的后处理
  • 某些工具应该终止 Agent 的推理循环

每个 ToolCallback 实现都可以定义是否将结果直接返回调用方。

配置方式:

1
2
3
4
5
6
7
// 声明式:@Tool 注解
@Tool(description = "...", returnDirect = true)

// 编程式:ToolMetadata
ToolMetadata metadata = ToolMetadata.builder()
.returnDirect(true)
.build();

ToolCallingManager 负责管理与工具关联的 returnDirect 属性。如果设为 true,工具调用结果直接返回调用方;否则结果回传给模型。


工具执行生命周期

ToolCallingManager 负责管理工具执行生命周期。如果使用 Spring AI Spring Boot Starters,DefaultToolCallingManager 是自动配置的默认实现。

Spring AI 支持三种工具执行生命周期管理模式:

模式 说明
框架托管(推荐) 通过 ChatClient 自动处理工具调用循环
Advisor 可控 自定义工具调用循环
用户手动控制 完全手动控制

框架托管(推荐 — ChatClient)

使用 ChatClient 时,Spring AI 通过 ToolCallingAdvisor 自动处理整个工具调用生命周期,该 Advisor 始终自动注册在 Advisor 链中(除非显式禁用)。

执行流程:
tool-calling-invoke-life

全局禁用自动注册:

1
spring.ai.chat.client.tool-calling-advisor.enabled: false

单条请求禁用:

1
2
3
4
chatClient.prompt()
.advisors(a -> a.param(AdvisorParams.TOOL_CALLING_ADVISOR_AUTO_REGISTER, false))
.user("...")
.call();

Advisor 可控(自定义循环)

ToolCallingAdvisor 将工具调用循环作为 Advisor 链的一部分实现,并暴露多个扩展点:

配置项 说明
toolCallingManager 使用的 ToolCallingManager 实例
advisorOrder Advisor 在链中的执行顺序
conversationHistoryEnabled 是否在工具调用迭代期间内部维护对话历史(默认 true
toolExecutionEligibilityChecker 决定模型响应是否应触发下一轮工具调用的 Function

对话历史管理:

  • 默认(conversationHistoryEnabled = true):ToolCallingAdvisor 在工具调用迭代期间内部维护完整对话历史
  • 默认排序:DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDERHIGHEST_PRECEDENCE + 200,低于 ToolCallingAdvisor.DEFAULT_ORDERHIGHEST_PRECEDENCE + 300
  • 使用 .disableInternalConversationHistory() 时需将 Memory Advisor 放在循环内部

Return Direct 支持:

ToolCallingAdvisor 支持”直接返回”特性。当工具执行的 returnDirect = true 时,Advisor 跳出工具调用循环并直接将工具结果返回。

用户手动控制

适用于需要自行控制工具执行生命周期的场景,例如向 UI 流式传输中间进度、添加自定义可观测性或在迭代间应用条件逻辑。

ChatClient 方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 禁用自动注册的 ToolCallingAdvisor
ChatClient.CallResponseSpec response = chatClient.prompt()
.advisors(a -> a.param(AdvisorParams.TOOL_CALLING_ADVISOR_AUTO_REGISTER, false))
.user("...")
.call();

// 手动驱动工具调用循环
ChatResponse chatResponse = response.chatResponse();
while (chatResponse.hasToolCalls()) {
// 使用 ToolCallingManager 执行工具调用
ToolExecutionResult result = toolCallingManager.execute(chatResponse);
// 将结果发回模型
chatResponse = chatClient.call(result.toPrompt());
}

流式 API 方式:

1
2
3
4
5
6
7
8
9
10
11
Flux<ChatResponse> stream = chatClient.prompt()
.advisors(a -> a.param(AdvisorParams.TOOL_CALLING_ADVISOR_AUTO_REGISTER, false))
.user("...")
.stream();

// 聚合流中的 chunk,然后检查工具调用
MessageAggregator aggregator = new MessageAggregator();
stream.subscribe(aggregator::add);

ChatResponse aggregated = aggregator.getChatResponse();
// 处理工具调用...

ChatModel 方式:

1
2
3
4
5
6
7
8
// 直接调用 ChatModel,不使用 ToolCallingAdvisor
ChatModel model = ...;
ChatResponse response = model.call(prompt);

while (response.hasToolCalls()) {
ToolExecutionResult result = toolCallingManager.execute(response);
response = model.call(result.toPrompt());
}

工具执行异常处理

工具执行失败时,异常以 ToolExecutionException 传播,可被捕获以处理错误。

ToolExecutionExceptionProcessor 用于处理 ToolExecutionException,有两种结果:

  • 产生一条错误消息回传给 AI 模型
  • 抛出异常由调用方处理

默认行为(DefaultToolExecutionExceptionProcessor):

  • RuntimeException 的错误消息回传给模型
  • 受检异常和 Error(如 IOExceptionOutOfMemoryError)始终抛出

配置:

1
spring.ai.tools.throw-exception-on-error: false
行为
true 工具调用错误作为异常抛出,由调用方处理
false(默认) 错误转换为消息并回传给 AI 模型,由其处理和响应

也可在构造函数中设置 alwaysThrow 属性。


动态工具解析(ToolCallbackResolver)

Spring AI 支持在运行时通过 ToolCallbackResolver 接口动态解析工具。

1
2
3
4
5
// 按名称注册工具
chatClient.prompt()
.tools("getWeather", "setAlarm")
.user("...")
.call();

ToolCallbackResolver 实现负责将工具名称解析为对应的 ToolCallback 实例。

  • 默认使用 StaticToolCallbackResolver,从静态 ToolCallback 列表中解析
  • Spring Boot 自动配置下,所有 ToolCallback 类型的 Bean 自动注册到解析器
  • 可提供自定义 ToolCallbackResolver Bean 覆盖默认逻辑

工具入参 Schema 增强

Spring AI 提供工具输入 Schema 动态增强功能,支持在不修改底层工具实现的情况下捕获额外信息(如推理过程、元数据等)。

常见用例:

  • 内部思考/推理: 在执行工具前捕获模型的逐步推理
  • 记忆增强: 提取洞察存储到长期记忆
  • 分析与追踪: 收集元数据、用户意图或使用模式
  • 多 Agent 协调: 传递 Agent 标识符或协调信号

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 定义增强参数作为 Java Record
record AgentThinking(String reasoning, String agentId) {}

// 2. 包装工具
AugmentedToolCallbackProvider augmented =
AugmentedToolCallbackProvider.wrap(toolCallbacks, AgentThinking.class);

// 3. 与 ChatClient 一起使用
chatClient.prompt()
.tools(augmented)
.user("...")
.call();

LLM 会看到带有额外字段的增强 Schema,而原始工具只接收其预期的参数。

核心类:

说明
AugmentedToolCallbackProvider 包装工具对象或 Provider,用指定 Record 类型增强所有工具
AugmentedToolCallback 包装单个 ToolCallback 实例
AugmentedArgumentEvent 包含 toolDefinition()rawInput()arguments() 供消费者使用
ToolInputSchemaAugmenter Schema 操作的底层工具类

removeExtraArgumentsAfterProcessing 选项控制是否将增强参数传递给原始工具:

  • true(默认)— 调用工具前移除增强参数
  • false — 在输入中保留增强参数(如果工具可以忽略额外字段)

可观测性与日志

  • 工具调用生成 spring.ai.tool 观测 Span(耗时 + 链路追踪信息传播)
  • 可选导出工具入参和结果为 Span Attribute(默认关闭,出于敏感性考虑)
  • 开启 DEBUG 日志:
1
logging.level.org.springframework.ai: DEBUG